-
Notifications
You must be signed in to change notification settings - Fork 5
Redesign repo structure and get domains list from multiple sources #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| Res.writeHead(403) | ||
| Res.end() | ||
| return | ||
| } else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) { |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 4 hours ago
In general, to fix uncontrolled path usage, normalize the user input, resolve it relative to a known safe root directory, and then verify that the resulting path is contained within that root. If the normalized path escapes the root, reject the request. For this server, the safe root is effectively ${process.cwd()}/dist. We should use path.resolve to join this root with the requested path, then ensure the resolved path starts with the root directory. This prevents directory traversal and absolute path attacks.
The best fix here is: (1) introduce an import of Node’s path module; (2) compute const distRoot = path.join(process.cwd(), 'dist') once per request (or outside the handler if appropriate); (3) take Req.url?.substring(1) || '' as the requested path segment, resolve it with path.resolve(distRoot, requestedPath), and (4) check that the resolved path starts with distRoot + path.sep or equals distRoot. If not, return 403. Then use this sanitized resolvedPath for both Fs.existsSync and Fs.readFileSync. This preserves existing functionality (files are still only served if in FileName, from loopback, and if they exist) while adding robust path validation.
Concretely in builder/source/utils/http-server.ts:
- Add
import * as Path from 'node:path'at the top. - Inside the request handler, compute
const distRoot = Path.join(process.cwd(), 'dist')andconst requestPath = Req.url?.substring(1) || ''. - Derive
const resolvedPath = Path.resolve(distRoot, requestPath). - Before the
FileName.includes/loopback/exists checks that depend on the path, add a guard: if!resolvedPath.startsWith(distRoot + Path.sep) && resolvedPath !== distRoot, respond with 403 and return. - Replace both occurrences of
`${process.cwd()}/dist/${Req.url?.substring(1)}`withresolvedPath.
No additional helper methods are strictly necessary beyond the new Path import and local variables.
-
Copy modified line R3 -
Copy modified lines R11-R22 -
Copy modified line R30 -
Copy modified line R36
| @@ -1,5 +1,6 @@ | ||
| import * as HTTP from 'node:http' | ||
| import * as Fs from 'node:fs' | ||
| import * as Path from 'node:path' | ||
|
|
||
| function IsLoopBack(IP: string) { | ||
| return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1' | ||
| @@ -7,7 +8,18 @@ | ||
|
|
||
| export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) { | ||
| const HTTPServer = HTTP.createServer((Req, Res) => { | ||
| if (!FileName.includes(Req.url?.substring(1) || '')) { | ||
| const distRoot = Path.join(process.cwd(), 'dist') | ||
| const requestPath = Req.url?.substring(1) || '' | ||
| const resolvedPath = Path.resolve(distRoot, requestPath) | ||
|
|
||
| // Ensure the resolved path stays within the dist root to prevent directory traversal | ||
| if (!resolvedPath.startsWith(distRoot + Path.sep) && resolvedPath !== distRoot) { | ||
| Res.writeHead(403) | ||
| Res.end() | ||
| return | ||
| } | ||
|
|
||
| if (!FileName.includes(requestPath)) { | ||
| Res.writeHead(404) | ||
| Res.end() | ||
| return | ||
| @@ -15,13 +27,13 @@ | ||
| Res.writeHead(403) | ||
| Res.end() | ||
| return | ||
| } else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) { | ||
| } else if (ShouldPreventHTTPResponse || !Fs.existsSync(resolvedPath)) { | ||
| Res.writeHead(503) | ||
| Res.end('File not built yet.') | ||
| return | ||
| } | ||
|
|
||
| const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8') | ||
| const Content = Fs.readFileSync(resolvedPath, 'utf-8') | ||
| Res.writeHead(200, { | ||
| 'content-type': 'application/javascript; charset=utf-8', | ||
| 'content-length': new TextEncoder().encode(Content).byteLength.toString() |
| return | ||
| } | ||
|
|
||
| const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8') |
Check failure
Code scanning / CodeQL
Uncontrolled data used in path expression High
user-provided value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 4 hours ago
In general, the fix is to ensure that any path derived from user input is constrained to a safe root directory. That means: (1) normalize the combined path (root + user input) using path.resolve (and optionally fs.realpathSync), and (2) verify that the normalized path is still under the intended root (dist), rejecting the request otherwise. Additionally, avoid using the raw URL for filesystem access; use the validated/normalized path instead.
For this snippet, the best minimal fix without changing functionality is:
- Introduce a constant
DIST_ROOTthat holds the absolute path to thedistdirectory, computed viapath.resolve(process.cwd(), 'dist'). - Import Node’s
pathmodule asimport * as Path from 'node:path'. - For each usage of
`${process.cwd()}/dist/${Req.url?.substring(1)}`, compute a safe path:- Take the requested path segment
Req.url?.substring(1) || ''. - Normalize it with
Path.normalize. - Resolve it against
DIST_ROOTwithPath.resolve(DIST_ROOT, requestedPath). - Optionally (more robustly) pass that resolved path through
Fs.realpathSyncin a try/catch to handle missing files cleanly. - Check that the final resolved path starts with
DIST_ROOT + Path.sep(or is exactlyDIST_ROOT) to ensure it doesn’t escape the root. If it does, respond with 403 and return.
- Take the requested path segment
- Replace both
Fs.existsSync(...)andFs.readFileSync(...)to use this validated resolved path (safePath) instead of reconstructing the string each time.
Concretely, inside RunDebugServer:
- Define
const DIST_ROOT = Path.resolve(process.cwd(), 'dist')near the top of the function. - Inside the request handler, once you’ve passed the
FileName.includes(...)and loopback checks, compute the safe path as described. - Use that safe path for
existsSyncandreadFileSync. - If resolving or
realpathSyncthrows or if the path check fails, respond with 403 (for traversal) or 503/404 as appropriate and return.
This keeps the existing behavior (only serving files in FileName, still returning 503 when the file is not built) while adding proper path safety.
-
Copy modified line R3 -
Copy modified lines R10-R11 -
Copy modified lines R13-R15 -
Copy modified lines R16-R30 -
Copy modified lines R36-R42
| @@ -1,13 +1,18 @@ | ||
| import * as HTTP from 'node:http' | ||
| import * as Fs from 'node:fs' | ||
| import * as Path from 'node:path' | ||
|
|
||
| function IsLoopBack(IP: string) { | ||
| return IP === '127.0.0.1' || IP === '::1' || IP === '::ffff:127.0.0.1' | ||
| } | ||
|
|
||
| export function RunDebugServer(Port: number, FileName: string[], ShouldPreventHTTPResponse: boolean) { | ||
| const DIST_ROOT = Path.resolve(process.cwd(), 'dist') | ||
|
|
||
| const HTTPServer = HTTP.createServer((Req, Res) => { | ||
| if (!FileName.includes(Req.url?.substring(1) || '')) { | ||
| const rawPath = Req.url?.substring(1) || '' | ||
|
|
||
| if (!FileName.includes(rawPath)) { | ||
| Res.writeHead(404) | ||
| Res.end() | ||
| return | ||
| @@ -15,13 +13,33 @@ | ||
| Res.writeHead(403) | ||
| Res.end() | ||
| return | ||
| } else if (ShouldPreventHTTPResponse || !Fs.existsSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`)) { | ||
| } | ||
|
|
||
| const normalizedRequestedPath = Path.normalize(rawPath) | ||
| const resolvedPath = Path.resolve(DIST_ROOT, normalizedRequestedPath) | ||
|
|
||
| if (resolvedPath !== DIST_ROOT && !resolvedPath.startsWith(DIST_ROOT + Path.sep)) { | ||
| Res.writeHead(403) | ||
| Res.end() | ||
| return | ||
| } | ||
|
|
||
| let safePath: string | ||
| try { | ||
| safePath = Fs.realpathSync(resolvedPath) | ||
| } catch { | ||
| Res.writeHead(503) | ||
| Res.end('File not built yet.') | ||
| return | ||
| } | ||
|
|
||
| const Content = Fs.readFileSync(`${process.cwd()}/dist/${Req.url?.substring(1)}`, 'utf-8') | ||
| if (ShouldPreventHTTPResponse || !Fs.existsSync(safePath)) { | ||
| Res.writeHead(503) | ||
| Res.end('File not built yet.') | ||
| return | ||
| } | ||
|
|
||
| const Content = Fs.readFileSync(safePath, 'utf-8') | ||
| Res.writeHead(200, { | ||
| 'content-type': 'application/javascript; charset=utf-8', | ||
| 'content-length': new TextEncoder().encode(Content).byteLength.toString() |
Fix #39
Fix #42
Fix #40